/*-----------------------------------------------------------------------------
* @file   hal_uart.c
* @brief  Implement interface of UART Module
* @author WLH<wanglehui8357@163.com>
-----------------------------------------------------------------------------*/
#include "hal_uart.h"
#include "board.h"
#include "ring_buf.h"

/*-----------------------------------------------------------------------------
 * COM Port Map Table:
 *---------------------
 * HAL_COM0 ==> LPUART0
 * HAL_COM1 ==> USART0
 * HAL_COM2 ==> UART4
 *---------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
 * Configure Macros
 ----------------------------------------------------------------------------*/
#define COM_DEV_CNT      3u
#define UART_TX_BUF_SIZE 256u
#define UART_RX_BUF_SIZE 256u

/*-----------------------------------------------------------------------------
 * local variable
-----------------------------------------------------------------------------*/
typedef struct
{
    /* The UART device handle       */

    /* Callback function for Rx ready */
    UART_RX_READY_CB cb_rx_ready;

    /* device exclusive access      */

    /* ringbuffer for received data */
    ring_buf_t* rb_rx;

    /* ringbuffer for send data     */
    ring_buf_t* rb_tx;
    U8 rx_buf[UART_RX_BUF_SIZE];
    U8 tx_buf[UART_TX_BUF_SIZE];
    U32 rx_ts;

    /* flag for device initialized: -1 fail, 1 ok, 0 not init */
    S8 state;

    volatile bool is_tx_done;
} gd32_serial_dev_t;

static gd32_serial_dev_t _serial_dev[COM_DEV_CNT];

/*-----------------------------------------------------------------------------
 * Local funtions
-----------------------------------------------------------------------------*/
/**
 * @brief configure LPUART
 */
static void lpusart_init(void)
{
    /* configure the CK_IRC16M as LPUART clock */
    rcu_lpuart_clock_config(IDX_LPUART0, RCU_LPUARTSRC_IRC16MDIV);

    /* enable GPIO&LPUART0 clock */
    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_LPUART0);

    gpio_af_set(GPIOA, GPIO_AF_8, GPIO_PIN_2);
    gpio_af_set(GPIOA, GPIO_AF_8, GPIO_PIN_3);

    /* configure LPUART0 TX as alternate function push-pull */
    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_2);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_10MHZ, GPIO_PIN_2);

    /* configure LPUART0 RX as alternate function push-pull */
    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_3);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_10MHZ, GPIO_PIN_3);

    /* LPUART0 configure */
    lpuart_deinit(LPUART0);
    lpuart_word_length_set(LPUART0, LPUART_WL_8BIT);
    lpuart_stop_bit_set(LPUART0, LPUART_STB_1BIT);
    lpuart_parity_config(LPUART0, LPUART_PM_NONE);
    lpuart_baudrate_set(LPUART0, 115200U);
    lpuart_receive_config(LPUART0, LPUART_RECEIVE_ENABLE);
    lpuart_transmit_config(LPUART0, LPUART_TRANSMIT_ENABLE);
    lpuart_enable(LPUART0);

    nvic_irq_enable(LPUART0_IRQn, 1);

    /* enable LPUART0 receive interrupt */
    lpuart_interrupt_enable(LPUART0, LPUART_INT_RBNE);

    /* enable LPUART0 transmit interrupt */
    lpuart_interrupt_enable(LPUART0, LPUART_INT_TBE);

    /* enable LPUART0 IDLE line interrupt */
    lpuart_interrupt_enable(LPUART0, LPUART_INT_IDLE);
}

static void usart0_init(void)
{
    /* enable COM GPIO clock */
    rcu_periph_clock_enable(RCU_GPIOA);

    /* enable USART clock */
    rcu_periph_clock_enable(RCU_USART0);

    /* connect port to USARTx_Tx */
    gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_9);

    /* connect port to USARTx_Rx */
    gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_10);

    /* configure USART Tx as alternate function push-pull */
    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_9);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_10MHZ, GPIO_PIN_9);

    /* configure USART Rx as alternate function push-pull */
    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_10);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_10MHZ, GPIO_PIN_10);

    /* USART configure */
    usart_deinit(USART0);
    usart_baudrate_set(USART0, 115200U);
    usart_receive_config(USART0, USART_RECEIVE_ENABLE);
    usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
    usart_enable(USART0);

    /* enable USART receive interrupt */
    usart_interrupt_enable(USART0, USART_INT_RBNE);

    /* enable USART transmit interrupt */
    usart_interrupt_enable(USART0, USART_INT_TBE);
}

static void uart4_init(void)
{
  
}

/*-----------------------------------------------------------------------------
 * public functions
-----------------------------------------------------------------------------*/
void hal_uart_global_init(void)
{
    /* enable DMA clock */
    rcu_periph_clock_enable(RCU_DMA);

    for(unsigned i=0 ;i<COM_DEV_CNT; i++)
    {
        _serial_dev[i].state = 0;
        _serial_dev[i].cb_rx_ready = NULL;
        _serial_dev[i].rb_tx = ringbuf_create(_serial_dev[i].tx_buf, UART_TX_BUF_SIZE);
        _serial_dev[i].rb_rx = ringbuf_create(_serial_dev[i].rx_buf, UART_RX_BUF_SIZE);
        _serial_dev[i].is_tx_done = true;
    }
}

bool hal_uart_init(const U8 port_num, const uart_cfg_t* cfg)
{
    if (port_num >= COM_DEV_CNT)
    {
        return false;
    }

    gd32_serial_dev_t* dev = &_serial_dev[port_num];
    if (HAL_COM0 == port_num)
    {
        lpusart_init();
        dev->state = 1u;
    }
    else if (HAL_COM1 == port_num)
    {
        usart0_init();
        dev->state = 1;
    }
    else if (HAL_COM2 == port_num)
    {
        uart4_init();
        dev->state = 1;
    }

    return false;
}

void hal_uart_reg_rx_ready(const U8 port_num, UART_RX_READY_CB cb_fun)
{
    if (port_num < COM_DEV_CNT)
    {
        gd32_serial_dev_t* dev = &_serial_dev[port_num];
        assert(NULL != dev);

        /* get device control */
        dev->cb_rx_ready = cb_fun;
    }
}

U32 hal_uart_read(const U8 port_num, U8* buf, U32 buf_size)
{
    if (port_num >= COM_DEV_CNT)
    {
        return 0U;
    }

    gd32_serial_dev_t *dev = &_serial_dev[port_num];

    if (1u != dev->state)
    {
        return 0U;
    }

    return ringbuf_read(dev->rb_rx, buf, buf_size);
}

U32 hal_uart_write(const U8 port_num, const U8* data, U32 data_size)
{
    if (port_num >= COM_DEV_CNT)
    {
        return 0U;
    }

    gd32_serial_dev_t *dev = &_serial_dev[port_num];

    /* move to tx ring buffer */
    ringbuf_write(dev->rb_tx, data, data_size);

    if (HAL_COM0 == port_num)
    {
        if(dev->is_tx_done)
        {
            dev->is_tx_done = false;
            lpuart_data_transmit(LPUART0, ringbuf_getc(dev->rb_tx));
            lpuart_interrupt_enable(LPUART0, LPUART_INT_TBE);
        }
    }
    else if (HAL_COM1 == port_num)
    {
        //
    }
    else if (HAL_COM2 == port_num)
    {
    }
    return data_size;
}

/*------------------------------------------------------------------------------
 * IRQHandlers for interrupt.
------------------------------------------------------------------------------*/
/**
 * @brief this function handles LPUART0 wakeup from deepsleep interrupt request
 */
void LPUART0_WKUP_IRQHandler(void)
{
    if (SET == lpuart_interrupt_flag_get(LPUART0, LPUART_INT_FLAG_WU))
    {
        lpuart_flag_clear(LPUART0, LPUART_FLAG_WU);
        exti_flag_clear(EXTI_28);
    }
}

void LPUART0_IRQHandler(void)
{
    if(RESET != lpuart_interrupt_flag_get(LPUART0, LPUART_INT_FLAG_RBNE))
    {
        /* read one byte from the receive data register */
        while(RESET != lpuart_interrupt_flag_get(LPUART0, LPUART_INT_FLAG_RBNE))
        {
            const U8 c = (U8)lpuart_data_receive(LPUART0);
            ringbuf_putc(_serial_dev[0].rb_rx, c);
        }
        lpuart_interrupt_flag_clear(LPUART0, LPUART_INT_FLAG_RBNE);
    }

    if(RESET != lpuart_interrupt_flag_get(LPUART0, LPUART_INT_FLAG_TBE))
    {
        if(ringbuf_is_empty(_serial_dev[0].rb_tx))
        {
            /* disable the LPUART0 transmit interrupt */
            _serial_dev[0].is_tx_done = true;
            lpuart_interrupt_disable(LPUART0, LPUART_INT_TBE);
        }
        else
        {
            /* write one byte to the transmit data register */
            lpuart_data_transmit(LPUART0, ringbuf_getc(_serial_dev[0].rb_tx));
        }
        lpuart_interrupt_flag_clear(LPUART0, LPUART_INT_FLAG_TBE);
    }

    if(RESET != lpuart_interrupt_flag_get(LPUART0, LPUART_INT_FLAG_IDLE))
    {
        trace_info("LPU0 interrupt: IDLE line detected");
        lpuart_interrupt_flag_clear(LPUART0, LPUART_INT_FLAG_IDLE);
        if(_serial_dev[0].cb_rx_ready)
        {
            _serial_dev[0].cb_rx_ready(HAL_COM0);
        }
    }

    if(RESET != lpuart_interrupt_flag_get(LPUART0, LPUART_INT_FLAG_AM))
    {
        trace_info("LPU0 interrupt: address match");
        lpuart_interrupt_flag_clear(LPUART0, LPUART_INT_FLAG_AM);
    }

    if(RESET != lpuart_interrupt_flag_get(LPUART0, LPUART_INT_FLAG_PERR))
    {
        trace_error("LPU0 interrupt: parity error");
        lpuart_interrupt_flag_clear(LPUART0, LPUART_INT_FLAG_PERR);
    }

    if(RESET != lpuart_interrupt_flag_get(LPUART0, LPUART_INT_FLAG_TC))
    {
        trace_info("LPU0 interrupt: transmission complete");
        lpuart_interrupt_flag_clear(LPUART0, LPUART_INT_FLAG_TC);
    }

    if(RESET != lpuart_interrupt_flag_get(LPUART0, LPUART_INT_FLAG_RBNE_ORERR))
    {
        trace_error("LPU0 interrupt: Rx buffer not empty interrupt and overrun error");
        lpuart_interrupt_flag_clear(LPUART0, LPUART_INT_FLAG_RBNE_ORERR);
    }

    if(RESET != lpuart_interrupt_flag_get(LPUART0, LPUART_INT_FLAG_WU))
    {
        trace_info("LPU0 interrupt: wakeup from deep-sleep mode");
        lpuart_interrupt_flag_clear(LPUART0, LPUART_INT_FLAG_WU);
    }

    // LPUART_INT_FLAG_CTS       // CTS interrupt and flag
    // LPUART_INT_FLAG_ERR_NERR  // interrupt and noise error flag
    // LPUART_INT_FLAG_ERR_ORERR // interrupt and overrun error flag
    // LPUART_INT_FLAG_ERR_FERR  // interrupt and frame error flag
}

void USART0_IRQHandler(void)
{
    if(RESET != usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE)) 
    {
        /* read one byte from the receive data register */
        //rx_buffer[rx_counter++] = (uint8_t)usart_data_receive(USART0);
        
        /* disable the USART0 receive interrupt */
        //usart_interrupt_disable(USART0, USART_INT_RBNE);
    }

    if(RESET != usart_interrupt_flag_get(USART0, USART_INT_FLAG_TBE)) 
    {
        /* write one byte to the transmit data register */
        // usart_data_transmit(USART0, tx_buffer[tx_counter++]);
        
        /* disable the USART0 transmit interrupt */
        usart_interrupt_disable(USART0, USART_INT_TBE);
    }
    
    if(RESET != usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE))
    {
        trace_info("USART0 interrupt: IDLE line detected");
        usart_interrupt_flag_clear(USART0, USART_INT_FLAG_IDLE);
        if(_serial_dev[1].cb_rx_ready)
        {
            _serial_dev[1].cb_rx_ready(HAL_COM1);
        }
    }    
}

void UART4_IRQHandler(void)
{
    if(RESET != usart_interrupt_flag_get(UART4, USART_INT_FLAG_RBNE)) 
    {
        /* read one byte from the receive data register */
        //rx_buffer[rx_counter++] = (uint8_t)usart_data_receive(UART4);
        
        /* disable the UART4 receive interrupt */
        //usart_interrupt_disable(UART4, USART_INT_RBNE);
    }

    if(RESET != usart_interrupt_flag_get(UART4, USART_INT_FLAG_TBE)) 
    {
        /* write one byte to the transmit data register */
        // usart_data_transmit(UART4, tx_buffer[tx_counter++]);
        
        /* disable the UART4 transmit interrupt */
        usart_interrupt_disable(UART4, USART_INT_TBE);
    }
    
    if(RESET != usart_interrupt_flag_get(UART4, USART_INT_FLAG_IDLE))
    {
        trace_info("UART4 interrupt: IDLE line detected");
        usart_interrupt_flag_clear(UART4, USART_INT_FLAG_IDLE);
        if(_serial_dev[2].cb_rx_ready)
        {
            _serial_dev[2].cb_rx_ready(HAL_COM2);
        }
    }
}
